#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <bsp.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/cdev.h>

#define I2C_ACCESS_CTRL_MAJOR            207
#define IOCTL_I2C_WRITE        _IO(I2C_ACCESS_CTRL_MAJOR, 1)
#define IOCTL_I2C_READ        _IO(I2C_ACCESS_CTRL_MAJOR, 2)

static struct class * cls_i2c_access;
struct i2c_access_dev {
	struct cdev cdev;
};
static struct i2c_access_dev * i2c_devp;

struct i2c_client * i2c_access_client = NULL;
static struct i2c_driver i2c_access_driver;
#define SENSOR_DEVICE_I2C 0x45
static unsigned short probe[] = { 0, SENSOR_DEVICE_I2C, I2C_CLIENT_END };
static unsigned short dummy[] = {I2C_CLIENT_END};

static struct i2c_client_address_data addr_data = {
	.normal_i2c = dummy,
	.probe = probe,
	.ignore = dummy,
};
struct i2c_reg {
	unsigned char reg;
	unsigned char val;
};

static int i2c_write(u8 reg, u8 val)
{
	unsigned char data[25];
	unsigned char bytes;

	data[0]= reg;
	data[1]=val;
	bytes = 2;

	if (i2c_master_send(i2c_access_client, data, bytes) == bytes)
	{
		mdelay(1);                
		return 0;
	}
	else
	{
		printk("write error!!!! \n");
		return -EIO;
	}
}


static u8 i2c_read(u8 reg)
{
	int ret;
	u8 data;
	ret = i2c_master_send(i2c_access_client, &reg, 1);
	if (ret < 0)
		return ret;
	ret = i2c_master_recv(i2c_access_client, &data, 1);
	if (ret != 1){
		printk(KERN_WARNING "Error while reading i2c\n");
		return -EIO;
	}
	return data;
}


static int i2c_control_open(struct inode *inode, struct file *file)
{
	return 0;
}


static int i2c_control_release(struct inode *inode, struct file *file)
{
	return 0;
}



static int i2c_control_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{
	u8 val;
	struct i2c_reg i2c_data;

	switch(cmd){
		case IOCTL_I2C_WRITE:
			if (copy_from_user((void *)&i2c_data,(const void *)arg, sizeof(struct i2c_reg))) {
				printk("(%s:%d) cannot copy data from user !!!\n", __func__, __LINE__);
				return -EFAULT;
			}
			i2c_write(i2c_data.reg, i2c_data.val);
			break;

		case IOCTL_I2C_READ:
			if (copy_from_user((void *)&i2c_data, arg, sizeof(struct i2c_reg))) {
				printk("(%s:%d) cannot copy data from user !!!\n", __func__, __LINE__);
				return -EFAULT;
			}
			i2c_data.val = i2c_read(i2c_data.reg);
			if (copy_to_user((void *)arg,(void *)&i2c_data, sizeof(struct i2c_reg))) {
				printk("(%s:%d) cannot copy data to user !!!\n", __func__, __LINE__);
				return -EFAULT;
			}
			break;

		default:
			printk("(%s:%d) unknown ioctl cmd (0x%x)\n", __func__, __LINE__, cmd);
			return -EINVAL;

	}

	return 0;
}



static struct file_operations i2c_control_fops = {
	.owner        = THIS_MODULE,
	.open        = i2c_control_open,
	.ioctl        = i2c_control_ioctl,
	.release    = i2c_control_release,
};
static int i2c_access_probe(struct i2c_adapter *adap, int addr, int kind)
{
	struct i2c_client *i2c;
	int ret;

	i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;
	strcpy(i2c->name, "i2c_access");
	i2c->flags = 0;
	i2c->addr = addr;
	i2c->adapter = adap;
	i2c->driver = &i2c_access_driver;

	i2c_access_client = i2c;

	/* attach i2c client device */
	ret = i2c_attach_client(i2c);
	if (ret < 0) {
		printk("%s: failed to attach codec at addr %x\n", __func__, addr);
		goto err;
	}

	return ret;
err:
	kfree(i2c);
	return ret;
}
static int i2c_access_attach(struct i2c_adapter *adap)
{
	return i2c_probe(adap, &addr_data, i2c_access_probe);
}

static int i2c_access_detach(struct i2c_client *client)
{
	i2c_detach_client(client);

	if(i2c_access_client)
	{
		kfree(i2c_access_client);
		i2c_access_client = NULL;
	}
	return 0;
}

static struct i2c_driver i2c_access_driver = {
	.driver = {
		.name = "tcc-i2c-access",
		.owner = THIS_MODULE,
	},
	.id = -1,
	.attach_adapter = i2c_access_attach,
	.detach_client = i2c_access_detach,
	.command = NULL,
};

static int __init i2c_access_init(void)
{
	int ret = 0;
	dev_t devno;
	int reg_val,reg_tref,reg_tmref;
	printk(KERN_INFO "Register i2c access driver.\n");


	ret = i2c_add_driver(&i2c_access_driver);
	if (ret != 0){
		printk(KERN_ERR "Failed to register I2C client.\n");
		return ENODEV;
	}

	devno = MKDEV(I2C_ACCESS_CTRL_MAJOR, 0);
	ret = register_chrdev_region(devno, 1, "i2c_access");

	if (ret){
		printk(KERN_INFO "register i2c_access_driver failed");
		return -ENODEV;
	}

	i2c_devp = (struct i2c_access_dev *)kmalloc(sizeof(struct i2c_access_dev), GFP_KERNEL);
	if (NULL == i2c_devp){
		printk(KERN_INFO "i2c_access:kmalloc failed");
		ret=-ENOMEM;
		goto fail_malloc;
	}
	memset(i2c_devp,0,sizeof(struct i2c_access_dev));
	cdev_init(&i2c_devp->cdev, &i2c_control_fops);
	i2c_devp->cdev.owner = THIS_MODULE;
	ret = cdev_add(&i2c_devp->cdev, devno, 1);
	if(ret) {
		printk(KERN_INFO "adding cdev failed");
		ret=-ENOMEM;
		goto fail_cdev;
	}
	cls_i2c_access=class_create(THIS_MODULE, "i2c_access");
	device_create(cls_i2c_access,NULL,devno,NULL,"i2c_access");

	return 0;
fail_cdev:
	kfree(i2c_devp);    
fail_malloc:
	unregister_chrdev_region(devno,1);
	return ret;
}
static void __exit i2c_access_exit(void)
{
	dev_t devno;
	printk(KERN_INFO "Unregister i2c access driver.\n");

	devno = MKDEV(I2C_ACCESS_CTRL_MAJOR, 0);
	device_destroy(cls_i2c_access,devno);
	class_destroy(cls_i2c_access);
	cdev_del(&i2c_devp->cdev);
	kfree(i2c_devp);
	unregister_chrdev_region(devno, 1);
}


module_init(i2c_access_init);
module_exit(i2c_access_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("J.H.Luo<sparkle-cliz@sohu.com>");
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("i2c access driver");
